/*-------------------------------------------------------------------------
 *
 * auto_explain.c
 *
 *
 * Copyright (c) 2008-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  contrib/auto_explain/auto_explain.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <limits.h>

#include "access/parallel.h"
#include "commands/explain.h"
#include "common/pg_prng.h"
#include "executor/instrument.h"
#include "jit/jit.h"
#include "utils/guc.h"

PG_MODULE_MAGIC;

/* GUC 变量 */
static int	auto_explain_log_min_duration = -1; /* 毫秒或 -1 */
static bool auto_explain_log_analyze = false;
static bool auto_explain_log_verbose = false;
static bool auto_explain_log_buffers = false;
static bool auto_explain_log_wal = false;
static bool auto_explain_log_triggers = false;
static bool auto_explain_log_timing = true;
static bool auto_explain_log_settings = false;
static int	auto_explain_log_format = EXPLAIN_FORMAT_TEXT;
static int	auto_explain_log_level = LOG;
static bool auto_explain_log_nested_statements = false;
static double auto_explain_sample_rate = 1;

static const struct config_enum_entry format_options[] = {
	{"text", EXPLAIN_FORMAT_TEXT, false},
	{"xml", EXPLAIN_FORMAT_XML, false},
	{"json", EXPLAIN_FORMAT_JSON, false},
	{"yaml", EXPLAIN_FORMAT_YAML, false},
	{NULL, 0, false}
};

static const struct config_enum_entry loglevel_options[] = {
	{"debug5", DEBUG5, false},
	{"debug4", DEBUG4, false},
	{"debug3", DEBUG3, false},
	{"debug2", DEBUG2, false},
	{"debug1", DEBUG1, false},
	{"debug", DEBUG2, true},
	{"info", INFO, false},
	{"notice", NOTICE, false},
	{"warning", WARNING, false},
	{"log", LOG, false},
	{NULL, 0, false}
};

/* 当前 ExecutorRun 调用的嵌套深度 */
static int	nesting_level = 0;

/* 当前顶层查询是否需要采样？ */
static bool current_query_sampled = false;

#define auto_explain_enabled() \
	(auto_explain_log_min_duration >= 0 && \
	 (nesting_level == 0 || auto_explain_log_nested_statements) && \
	 current_query_sampled)

/* 在卸载时保存的钩子值 */
static ExecutorStart_hook_type prev_ExecutorStart = NULL;
static ExecutorRun_hook_type prev_ExecutorRun = NULL;
static ExecutorFinish_hook_type prev_ExecutorFinish = NULL;
static ExecutorEnd_hook_type prev_ExecutorEnd = NULL;

void		_PG_init(void);

static void fc_explain_ExecutorStart(QueryDesc *fc_queryDesc, int fc_eflags);
static void fc_explain_ExecutorRun(QueryDesc *fc_queryDesc,
								ScanDirection fc_direction,
								uint64 fc_count, bool fc_execute_once);
static void fc_explain_ExecutorFinish(QueryDesc *fc_queryDesc);
static void fc_explain_ExecutorEnd(QueryDesc *fc_queryDesc);


/*
 * 模块加载回调
 */
void _PG_init(void)
{
	/* 定义自定义 GUC 变量。 */
	DefineCustomIntVariable("auto_explain.log_min_duration",
							"Sets the minimum execution time above which plans will be logged.",
							"Zero prints all plans. -1 turns this feature off.",
							&auto_explain_log_min_duration,
							-1,
							-1, INT_MAX,
							PGC_SUSET,
							GUC_UNIT_MS,
							NULL,
							NULL,
							NULL);

	DefineCustomBoolVariable("auto_explain.log_analyze",
							 "Use EXPLAIN ANALYZE for plan logging.",
							 NULL,
							 &auto_explain_log_analyze,
							 false,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);

	DefineCustomBoolVariable("auto_explain.log_settings",
							 "Log modified configuration parameters affecting query planning.",
							 NULL,
							 &auto_explain_log_settings,
							 false,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);

	DefineCustomBoolVariable("auto_explain.log_verbose",
							 "Use EXPLAIN VERBOSE for plan logging.",
							 NULL,
							 &auto_explain_log_verbose,
							 false,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);

	DefineCustomBoolVariable("auto_explain.log_buffers",
							 "Log buffers usage.",
							 NULL,
							 &auto_explain_log_buffers,
							 false,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);

	DefineCustomBoolVariable("auto_explain.log_wal",
							 "Log WAL usage.",
							 NULL,
							 &auto_explain_log_wal,
							 false,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);

	DefineCustomBoolVariable("auto_explain.log_triggers",
							 "Include trigger statistics in plans.",
							 "This has no effect unless log_analyze is also set.",
							 &auto_explain_log_triggers,
							 false,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);

	DefineCustomEnumVariable("auto_explain.log_format",
							 "EXPLAIN format to be used for plan logging.",
							 NULL,
							 &auto_explain_log_format,
							 EXPLAIN_FORMAT_TEXT,
							 format_options,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);

	DefineCustomEnumVariable("auto_explain.log_level",
							 "Log level for the plan.",
							 NULL,
							 &auto_explain_log_level,
							 LOG,
							 loglevel_options,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);

	DefineCustomBoolVariable("auto_explain.log_nested_statements",
							 "Log nested statements.",
							 NULL,
							 &auto_explain_log_nested_statements,
							 false,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);

	DefineCustomBoolVariable("auto_explain.log_timing",
							 "Collect timing data, not just row counts.",
							 NULL,
							 &auto_explain_log_timing,
							 true,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);

	DefineCustomRealVariable("auto_explain.sample_rate",
							 "Fraction of queries to process.",
							 NULL,
							 &auto_explain_sample_rate,
							 1.0,
							 0.0,
							 1.0,
							 PGC_SUSET,
							 0,
							 NULL,
							 NULL,
							 NULL);

	MarkGUCPrefixReserved("auto_explain");

	/* 安装钩子。 */
	prev_ExecutorStart = ExecutorStart_hook;
	ExecutorStart_hook = fc_explain_ExecutorStart;
	prev_ExecutorRun = ExecutorRun_hook;
	ExecutorRun_hook = fc_explain_ExecutorRun;
	prev_ExecutorFinish = ExecutorFinish_hook;
	ExecutorFinish_hook = fc_explain_ExecutorFinish;
	prev_ExecutorEnd = ExecutorEnd_hook;
	ExecutorEnd_hook = fc_explain_ExecutorEnd;
}

/*
 * ExecutorStart 钩子：如果需要，启动日志记录
 */
static void fc_explain_ExecutorStart(QueryDesc *fc_queryDesc, int fc_eflags)
{
	/*
	 * 在每个顶层语句的开始，决定是否对该语句进行
	 * 采样。如果启用了嵌套语句解释，
	 * 则所有嵌套语句都将被解释或都不将被解释。
	 *
	 * 当在并行工作进程中时，我们应什么也不做，
	 * 我们可以通过假装决定不对当前语句进行采样来便宜地实现
	 * 这一点。如果父会话中启用了 EXPLAIN，数据将被收集并
	 * 返回给父会话，我们无权干扰。
	 */
	if (nesting_level == 0)
	{
		if (auto_explain_log_min_duration >= 0 && !IsParallelWorker())
			current_query_sampled = (pg_prng_double(&pg_global_prng_state) < auto_explain_sample_rate);
		else
			current_query_sampled = false;
	}

	if (auto_explain_enabled())
	{
		/* 仅在需要 log_analyze 时启用每个节点的监控。 */
		if (auto_explain_log_analyze && (fc_eflags & EXEC_FLAG_EXPLAIN_ONLY) == 0)
		{
			if (auto_explain_log_timing)
				fc_queryDesc->instrument_options |= INSTRUMENT_TIMER;
			else
				fc_queryDesc->instrument_options |= INSTRUMENT_ROWS;
			if (auto_explain_log_buffers)
				fc_queryDesc->instrument_options |= INSTRUMENT_BUFFERS;
			if (auto_explain_log_wal)
				fc_queryDesc->instrument_options |= INSTRUMENT_WAL;
		}
	}

	if (prev_ExecutorStart)
		prev_ExecutorStart(fc_queryDesc, fc_eflags);
	else
		standard_ExecutorStart(fc_queryDesc, fc_eflags);

	if (auto_explain_enabled())
	{
		/*
		 * 设置以跟踪 ExecutorRun 中的总耗时。确保
		 * 在每个查询上下文中分配空间，以便在
		 * ExecutorEnd 时释放。
		 */
		if (fc_queryDesc->totaltime == NULL)
		{
			MemoryContext fc_oldcxt;

			fc_oldcxt = MemoryContextSwitchTo(fc_queryDesc->estate->es_query_cxt);
			fc_queryDesc->totaltime = InstrAlloc(1, INSTRUMENT_ALL, false);
			MemoryContextSwitchTo(fc_oldcxt);
		}
	}
}

/*
 * ExecutorRun 钩子：我们需要做的就是跟踪嵌套深度
 */
static void fc_explain_ExecutorRun(QueryDesc *fc_queryDesc, ScanDirection fc_direction,
					uint64 fc_count, bool fc_execute_once)
{
	nesting_level++;
	PG_TRY();
	{
		if (prev_ExecutorRun)
			prev_ExecutorRun(fc_queryDesc, fc_direction, fc_count, fc_execute_once);
		else
			standard_ExecutorRun(fc_queryDesc, fc_direction, fc_count, fc_execute_once);
	}
	PG_FINALLY();
	{
		nesting_level--;
	}
	PG_END_TRY();
}

/*
 * ExecutorFinish 钩子：我们需要做的就是跟踪嵌套深度
 */
static void fc_explain_ExecutorFinish(QueryDesc *fc_queryDesc)
{
	nesting_level++;
	PG_TRY();
	{
		if (prev_ExecutorFinish)
			prev_ExecutorFinish(fc_queryDesc);
		else
			standard_ExecutorFinish(fc_queryDesc);
	}
	PG_FINALLY();
	{
		nesting_level--;
	}
	PG_END_TRY();
}

/*
 * ExecutorEnd 钩子：如有需要，记录结果
 */
static void fc_explain_ExecutorEnd(QueryDesc *fc_queryDesc)
{
	if (fc_queryDesc->totaltime && auto_explain_enabled())
	{
		MemoryContext fc_oldcxt;
		double		fc_msec;

		/*
		 * 确保我们在每个查询上下文中操作，以便在
		 * ExecutorEnd 时将任何杂物丢弃。
		 */
		fc_oldcxt = MemoryContextSwitchTo(fc_queryDesc->estate->es_query_cxt);

		/*
		 * 确保统计信息累计完成。（注意：如果多个
		 * 钩子都执行此操作也是可以的。）
		 */
		InstrEndLoop(fc_queryDesc->totaltime);

		/* 如果超出时长则记录计划。 */
		fc_msec = fc_queryDesc->totaltime->total * 1000.0;
		if (fc_msec >= auto_explain_log_min_duration)
		{
			ExplainState *fc_es = NewExplainState();

			fc_es->analyze = (fc_queryDesc->instrument_options && auto_explain_log_analyze);
			fc_es->verbose = auto_explain_log_verbose;
			fc_es->buffers = (fc_es->analyze && auto_explain_log_buffers);
			fc_es->wal = (fc_es->analyze && auto_explain_log_wal);
			fc_es->timing = (fc_es->analyze && auto_explain_log_timing);
			fc_es->summary = fc_es->analyze;
			fc_es->format = auto_explain_log_format;
			fc_es->settings = auto_explain_log_settings;

			ExplainBeginOutput(fc_es);
			ExplainQueryText(fc_es, fc_queryDesc);
			ExplainPrintPlan(fc_es, fc_queryDesc);
			if (fc_es->analyze && auto_explain_log_triggers)
				ExplainPrintTriggers(fc_es, fc_queryDesc);
			if (fc_es->costs)
				ExplainPrintJITSummary(fc_es, fc_queryDesc);
			ExplainEndOutput(fc_es);

			/* 移除最后的换行符 */
			if (fc_es->str->len > 0 && fc_es->str->data[fc_es->str->len - 1] == '\n')
				fc_es->str->data[--fc_es->str->len] = '\0';

			/* 修复 JSON 以输出对象 */
			if (auto_explain_log_format == EXPLAIN_FORMAT_JSON)
			{
				fc_es->str->data[0] = '{';
				fc_es->str->data[fc_es->str->len - 1] = '}';
			}

			/*
			 * 注意：我们依赖于现有的上下文日志或
			 * debug_query_string 来识别正在
			 * 报告的具体语句。这并不理想，但在
			 * 这里尝试做这件事通常会导致重复。
			 */
			ereport(auto_explain_log_level,
					(errmsg("duration: %.3f ms  plan:\n%s",
							fc_msec, fc_es->str->data),
					 errhidestmt(true)));
		}

		MemoryContextSwitchTo(fc_oldcxt);
	}

	if (prev_ExecutorEnd)
		prev_ExecutorEnd(fc_queryDesc);
	else
		standard_ExecutorEnd(fc_queryDesc);
}
